/* 'Magic' date parsing, by Simon Willison (6th October 2003)
   http://simon.incutio.com/archive/2003/10/06/betterDateInput
*/

var monthNames = "january february march april may june july august september october november december".split(" ");
var weekdayNames = "sunday monday tuesday wednesday thursday friday saturday".split(" ");

/* Takes a string, returns the index of the month matching that string, throws
   an error if 0 or more than 1 matches
*/
function parseMonth(month) {
	var matches = new Array();
	month = month.toLowerCase();
	for (var i = 0; i < monthNames.length; i++)
		if (monthNames[i].indexOf(month) == 0) matches.push(monthNames[i]);
	if (matches.length == 0) {
		throw new Error("Invalid month string");
	}
	if (matches.length > 1) {
		throw new Error("Ambiguous month");
	}
	return monthNames.indexOf(matches[0]);
}
/* Same as parseMonth but for days of the week */
function parseWeekday(weekday) {
	var matches = new Array();
	weekday = weekday.toLowerCase();
	for (var i = 0; i < weekdayNames.length; i++)
		if (weekdayNames[i].indexOf(weekday) == 0) matches.push(weekdayNames[i]);
	if (matches.length == 0) {
		throw new Error("Invalid day string");
	}
	if (matches.length > 1) {
		throw new Error("Ambiguous weekday");
	}
	return weekdayNames.indexOf(matches[0]);
}
/* New date by specifying date, month (0 based), year */
function newDate(d, m, y) {
	var r = new Date();
	if (y != null)	r.setYear(y);
	if (m != null)	{r.setDate(1); r.setMonth(m);}  //Simply set day 1, in case month is not compatible with date. Since no call with month and without day, so it's safe
	if (d != null)	r.setDate(d);
	if (d != null && r.getDate() != d)	throw new Error("Invalid date");
	if (m != null && r.getMonth() != m)	throw new Error("Invalid month");
	if (y != null && r.getYear() != y && r.getYear() != y - 1900)	throw new Error("Invalid year");
	return r;
}

/* Array of objects, each has 're', a regular expression and 'handler', a 
   function for creating a date from something that matches the regular 
   expression. Handlers may throw errors if string is unparseable. 
*/
var dateParsePatterns = [
	// Today
	{   re: /^tod/i,
		handler: function() { 
			return new Date();
		} 
	},
	// Tomorrow
	{   re: /^tom/i,
		handler: function() {
			var d = new Date(); 
			d.setDate(d.getDate() + 1); 
			return d;
		}
	},
	// Yesterday
	{   re: /^yes/i,
		handler: function() {
			var d = new Date();
			d.setDate(d.getDate() - 1);
			return d;
		}
	},
	// mm/dd/yyyy (American style)
	{   re: /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/,
		handler: function(bits) {
			return newDate(parseInt(bits[2], 10), parseInt(bits[1], 10) - 1, parseInt(bits[3]));
		}
	},
	// yyyy-mm-dd (ISO style)
	{   re: /^(\d{4})-(\d{1,2})-(\d{1,2})$/,
		handler: function(bits) {
			return newDate(parseInt(bits[3], 10), parseInt(bits[2], 10) - 1, parseInt(bits[1]));
		}
	},
	// mm/dd or mm-dd
	{   re: /^(\d{1,2})[-/](\d{1,2})$/,
		handler: function(bits) {
			return newDate(parseInt(bits[2], 10), parseInt(bits[1], 10) - 1);
		}
	},
	// 4 or 4th
	{   re: /^(\d{1,2})(st|nd|rd|th)?$/i, 
		handler: function(bits) {
			return newDate(parseInt(bits[1], 10));
		}
	},
	// 4th Jan
	{   re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i, 
		handler: function(bits) {
			return newDate(parseInt(bits[1], 10), parseMonth(bits[2]));
		}
	},
	// 4th Jan 2003
	{   re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
		handler: function(bits) {
			return newDate(parseInt(bits[1], 10), parseMonth(bits[2]), parseInt(bits[3]));
		}
	},
	// Jan 4th
	{   re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i, 
		handler: function(bits) {
			return newDate(parseInt(bits[2], 10), parseMonth(bits[1]));
		}
	},
	// Jan 4th 2003
	{   re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
		handler: function(bits) {
			return newDate(parseInt(bits[2], 10), parseMonth(bits[1]), parseInt(bits[3]));
		}
	},
	// this Tuesday
	{   re: /^this (\w+)$/i,
		handler: function(bits) {
			var d = new Date();
			var addDays = parseWeekday(bits[1]) - d.getDay();
			d.setDate(d.getDate() + addDays);
			return d;
		}
	},
	// next Tuesday
	{   re: /^next (\w+)$/i,
		handler: function(bits) {
			var d = new Date();
			var addDays = parseWeekday(bits[1]) - d.getDay() + 7;
			d.setDate(d.getDate() + addDays);
			return d;
		}
	},
	// last Tuesday
	{   re: /^last (\w+)$/i,
		handler: function(bits) {
			var d = new Date();
			var addDays = parseWeekday(bits[1]) - d.getDay() - 7;
			d.setDate(d.getDate() + addDays);
			return d;
		}
	}
];

function parseDateString(s) {
	for (var i = 0; i < dateParsePatterns.length; i++) {
		var re = dateParsePatterns[i].re;
		var handler = dateParsePatterns[i].handler;
		var bits = re.exec(s);
		if (bits) {
			return handler(bits);
		}
	}
	throw new Error("Invalid date string");
}
